热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

可能会|前文_c++虚表学习2

篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。 前文 本文会让读者明白虚表原理,理解类的大致内存结构。棱形继承下内存布局&#xf

篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。



前文
  1. 本文会让读者明白虚表原理,
  2. 理解类的大致内存结构。棱形继承下内存布局,和虚继承单一内存布局情况。
  3. 父类构造函数调用虚函数会怎么样
  4. 父类析构函数调用虚函数情况
  5. 为什么析构函数一定要是虚函数

单继承

我们首先参阅如下的代码:

class Person
public:
int pFlag = 2;
Person()
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");

XH()
printf("XH \\r\\n");

virtual void vSayXH()
printf("vSayXH \\r\\n");

virtual void vSayXH2()
printf("vSayXH2 \\r\\n");

void nSayXH()
printf("nSayXH \\r\\n");

;
int main()


XH* pXh = new XH();
pXh->nSayPerson();
pXh->vSayPerson();
pXh->vSayXH2();
pXh->nSayXH();
delete pXh;


return 0;

Debug编译后的汇编代码


我们跟进到构造函数中:

我们按照上面的顺序逐个分析


  1. 调用父类的构造函数。因为子类可能会用到父类的东西
  2. 给自己虚表赋值,注意虚表在内存首地址。
  3. 给自己变量赋值 。在上面你注意 [eax+8] 这个地址不是eax+4,证明中间还有其他东西。这里存放的是父类的局部变量
  4. 调用自身构造方法内的函数体

我们首先构建出整个内存图:

构造函数要先调父类的初始化函数:
因为子类会用到父类资源,比如子类获取父类的变量

先初始化虚表指针在调用属性初始化和方法体:
因为构造函数会有可能调用虚函数

先初始化属性在调用方法体:
方法体可能会获取属性

我们首先虚表的赋值代码:

我们接下来看下Person这个类的初始化函数



你会差异的发现父类构造也会填入自己虚表,完成父类构造的后,子类又会覆盖写入这个虚表地址。
这样会有什么关系和异常呢?假设子类重写父类的虚函数,在父类构造函数调用虚函数只会调用自己的函数而不是子类的。

我们看下XH虚表地址的交叉引用信息

我们观察下XH析构函数:

为啥需要在自己析构函数中再次给自己的虚表赋值呢?为了解答这个答案我们首先才看~Person析构


在析构对象流程,首先释放子类的所有子类资源,在释放父类所有资源。因为子类资源被释放了,如果调用到父类时虚表没有还原父类的虚表,那么父类析构中有调用虚函数的可能会引起意外的异常。因为指向的函数是一个释放资源的子类函数。

我们最后看看几个虚函数和非虚函数的调用

pXh->nSayPerson();
pXh->vSayPerson();


首先我们要知道的是XH的虚表中第二项就是vSayPerson函数地址,第一项是析构函数代理函数地址。

现在你应该对虚函数的调用有一定的认识了吧。现在你应该举一反三回答出为啥析构函数为啥一定要是虚函数了。。。

我们现在重写Person虚函数看看XH的虚表会怎么样.


class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");

XH()
printf("XH \\r\\n");

virtual void vSayXH()
printf("vSayXH \\r\\n");

virtual void vSayXH2()
printf("vSayXH2 \\r\\n");

void nSayXH()
printf("nSayXH \\r\\n");

//重写
virtual void vSayPerson()
printf("XH vSayPerson\\r\\n");

;


多继承

class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");

virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");

;
class Person : public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class Female : public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");

virtual ~Female()
printf(" ~Female \\r\\n");

virtual void vSayFemale()
printf("vSayFemale \\r\\n");

void nSayFemale()
printf("nSayFemale \\r\\n");

;

class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");

virtual ~XM()
printf("~XM \\r\\n");

XM()
printf("XM \\r\\n");

virtual void sayXm()
printf("sayXm \\r\\n");

;

int main()

XM* pXm = new XM();
//280
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;


我们可以看到多继承下XM输出类大小是280字节,我们改用虚继承

class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");

virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");

;
class Person : virtual public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class Female :virtual public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");

virtual ~Female()
printf(" ~Female \\r\\n");

virtual void vSayFemale()
printf("vSayFemale \\r\\n");

void nSayFemale()
printf("nSayFemale \\r\\n");

;

class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");

virtual ~XM()
printf("~XM \\r\\n");

XM():BaseClass(0x123)
printf("XM \\r\\n");

virtual void sayXm()
printf("sayXm \\r\\n");

;

int main()

XM* pXm = new XM();
//280
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;


普通多继承类内存视图

可见虚继承减少部分内存,我们首先研究下分非虚继承下的XM内存结构

我们直接查看XM的构造函数

析构方法


多继承类内存视图

我们知道虚继承可减少共同父类占用空间,比如本例中XM类会有两个BaseClass类,因此我们会想280减去一个Base类大小就是虚继承后的大小。具体数值为 144=280-136,但是我们通过允许后发现实际内存是160大小。

//..其他代码略,这里是虚继承的代码
int main()

XM* pXm = new XM();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;


我们很明显发现FemalePerson大小变大了8字节.

为了研究这个问题们修改以下代码首先构造一个Female


int main()

Female* pXm = new Female();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;

我们这里为方便理解直接给出虚继承内存结构:

IDA PRO 查看构造函数你会发现这个代码会有分支结构,有可能不会调用虚基类初始化函数


我们最后再看看XM这个类的内存结构体:



推荐阅读
author-avatar
Missluckyyy_879
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有